package net.lulihu.common_util.jdbc;

import lombok.extern.slf4j.Slf4j;
import net.lulihu.Assert;
import net.lulihu.ObjectKit.LogKit;
import net.lulihu.ObjectKit.MapKit;
import net.lulihu.dataStructure.LRULinkedHashMap;
import net.lulihu.exception.ToolBoxException;

import javax.sql.DataSource;
import java.util.Iterator;
import java.util.Map;

/**
 * 多数据源管理
 */
@Slf4j
public class MultipleDataSourceManage {

    private final LRULinkedHashMap<String, DataSource> multipleDataSource;
    private final LRULinkedHashMap<String, DataSourceOperational> multipleDataSourceOperational;

    //默认数据源
    private DataSource defaultDataSource;
    // 默认数据源名称
    private String defaultDataSourceName;

    // 构造函数私有化
    private MultipleDataSourceManage() {
        this.multipleDataSource = new LRULinkedHashMap<>();
        this.multipleDataSourceOperational = new LRULinkedHashMap<>();
    }

    /**
     * 获取实例
     *
     * @return MultipleDataSourceManage
     */
    public static MultipleDataSourceManage getInstance() {
        return MultipleDataSourceManageSingleEnum.INSTANCE.getMultipleDataSourceManage();
    }

    /**
     * 添加数据源
     * <p>
     * 如果未指定数据源为默认数据源则将第一个添加的数据源作为默认数据源
     *
     * @param name   数据源引用名称
     * @param config 数据源配置
     */
    public synchronized void addDataSource(String name, DatabaseConfig config) {
        addDataSource(name, config, new DefaultDataSourceOperational());
    }

    public synchronized void addDataSource(String name, DatabaseConfig config, DataSourceOperational dataSourceOperational) {
        Assert.notNull(name, "数据源不可以为空...");
        Assert.notNull(config, "数据源配置不可以为空...");
        Assert.notNull(dataSourceOperational, "数据源操作程序不可以为空...");

        //初始化
        DataSource dataSource = dataSourceOperational.initDataSource(config);
        this.multipleDataSource.put(name, dataSource);
        this.multipleDataSourceOperational.put(name, dataSourceOperational);
        // 设置默认数据源
        if (config.isDefaultDataSource()) setDefaultDataSource(name);
        else if (this.defaultDataSource == null) setDefaultDataSource(name);
    }

    /**
     * 直接添加数据源
     * <p>
     * 这种方式添加的数据源不做生命周期管理
     *
     * @param name       数据源名称
     * @param dataSource 数据源
     */
    public synchronized void addDataSource(String name, DataSource dataSource) {
        this.multipleDataSource.put(name, dataSource);
        if (this.defaultDataSource == null) setDefaultDataSource(name);
    }

    /**
     * 获取数据源
     *
     * @param name 数据源名称
     * @return 数据源
     */
    public DataSource getDataSource(String name) {
        return this.multipleDataSource.computeIfAbsent(name, k -> {
            throw new ToolBoxException("名称为{}的数据源不存在...");
        });
    }

    /**
     * 关闭所有数据源
     */
    public void shutdown() {
        synchronized (this) {
            Iterator<Map.Entry<String, DataSource>> iterator = this.multipleDataSource.entrySet().iterator();

            while (iterator.hasNext()) {
                Map.Entry<String, DataSource> next = iterator.next();
                String name = next.getKey();
                try {
                    DataSourceOperational operational = this.multipleDataSourceOperational.remove(name);
                    if (operational != null) operational.close(next.getValue());
                } catch (Exception e) {
                    LogKit.error(log, "关闭数据源【{}】时发生错误", name, e);
                }
                iterator.remove();
            }
            LogKit.info(log, "数据源已经全部关闭...");
        }
    }

    /**
     * 关闭指定数据源
     *
     * @param name 数据源名称
     */
    public void shutdown(String name) {
        synchronized (this) {
            DataSource dataSource = this.multipleDataSource.computeIfAbsent(name, k -> {
                throw new ToolBoxException("名称为{}的数据源不存在...");
            });

            try {
                DataSourceOperational operational = this.multipleDataSourceOperational.remove(name);
                if (operational != null)
                    operational.close(dataSource);
            } catch (Exception e) {
                LogKit.error(log, "关闭数据源【{}】时发生错误", name, e);
            }

            this.multipleDataSource.remove(name);
            // 如果关闭的是默认数据源则随机指定一个
            if (dataSource.equals(defaultDataSource)) {
                if (MapKit.isNotEmpty(this.multipleDataSource)) {
                    Map.Entry<String, DataSource> head = this.multipleDataSource.getHead();
                    LogKit.warn(log, "默认数据源被关闭，将【{}】设置为默认数据源", head.getKey());
                    this.defaultDataSource = head.getValue();
                    return;
                }
                LogKit.warn(log, "注意:数据源已经被全部清除,继续运行可能产生意料之外的错误...");
            }
        }
    }

    /**
     * 设置默认数据源
     *
     * @param name 数据源名称
     */
    public synchronized void setDefaultDataSource(String name) {
        if (this.multipleDataSource.containsKey(name)) {
            this.defaultDataSource = this.multipleDataSource.get(name);
            this.defaultDataSourceName = name;
        } else throw new ToolBoxException("名称为{}的数据源不存在...");
    }

    /**
     * 获取默认数据源
     *
     * @return 数据源
     */
    public DataSource getDefaultDataSource() {
        Assert.notNull(this.defaultDataSource, "未指定默认数据源或未设置任何数据源...");
        return this.defaultDataSource;
    }

    /**
     * 获取默认数据源名称
     *
     * @return 数据源名称
     */
    public String getDefaultDataSourceName() {
        Assert.notNull(this.defaultDataSourceName, "未指定默认数据源或未设置任何数据源...");
        return this.defaultDataSourceName;
    }

    /**
     * 枚举式单例
     */
    enum MultipleDataSourceManageSingleEnum {

        INSTANCE;

        MultipleDataSourceManageSingleEnum() {
            this.multipleDataSourceManage = new MultipleDataSourceManage();
        }

        private MultipleDataSourceManage multipleDataSourceManage;

        public MultipleDataSourceManage getMultipleDataSourceManage() {
            return this.multipleDataSourceManage;
        }
    }

}
